home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 7: Sunsite / Linux Cubed Series 7 - Sunsite Vol 1.iso / system / news / inn1.000 / inn1.4sec-linux-src.tar / inn / nnrpd / article.c next >
C/C++ Source or Header  |  1993-03-18  |  23KB  |  1,109 lines

  1. /*  $Revision: 1.13 $
  2. **
  3. **  Article-related routines.
  4. */
  5. #include "nnrpd.h"
  6.  
  7.  
  8. /*
  9. **  Data structures for use in ARTICLE/HEAD/BODY/STAT common code.
  10. */
  11. typedef enum _SENDTYPE {
  12.     STarticle,
  13.     SThead,
  14.     STbody,
  15.     STstat
  16. } SENDTYPE;
  17.  
  18. typedef struct _SENDDATA {
  19.     SENDTYPE    Type;
  20.     int        ReplyCode;
  21.     STRING    Item;
  22. } SENDDATA;
  23.  
  24.  
  25. /*
  26. **  Information about the schema of the news overview files.
  27. */
  28. typedef struct _ARTOVERFIELD {
  29.     char    *Header;
  30.     int        Length;
  31.     BOOL    HasHeader;
  32. } ARTOVERFIELD;
  33.  
  34.  
  35. STATIC char        ARTnotingroup[] = NNTP_NOTINGROUP;
  36. STATIC char        ARTnoartingroup[] = NNTP_NOARTINGRP;
  37. STATIC char        ARTnocurrart[] = NNTP_NOCURRART;
  38. STATIC QIOSTATE        *ARTqp;
  39. STATIC ARTOVERFIELD    *ARTfields;
  40. STATIC int        ARTfieldsize;
  41. STATIC SENDDATA        SENDbody = {
  42.     STbody,    NNTP_BODY_FOLLOWS_VAL,        "body"
  43. };
  44. STATIC SENDDATA        SENDarticle = {
  45.     STarticle,    NNTP_ARTICLE_FOLLOWS_VAL,    "article"
  46. };
  47. STATIC SENDDATA        SENDstat = {
  48.     STstat,    NNTP_NOTHING_FOLLOWS_VAL,    "status"
  49. };
  50. STATIC SENDDATA        SENDhead = {
  51.     SThead,    NNTP_HEAD_FOLLOWS_VAL,        "head"
  52. };
  53.  
  54.  
  55. /*
  56. **  Overview state information.
  57. */
  58. STATIC QIOSTATE        *OVERqp;        /* Open overview file    */
  59. STATIC char        *OVERline;        /* Current line        */
  60. STATIC ARTNUM        OVERarticle;        /* Current article    */
  61. STATIC int        OVERopens;        /* Number of opens done    */
  62.  
  63.  
  64. /*
  65. **  Read the overview schema.
  66. */
  67. void
  68. ARTreadschema()
  69. {
  70.     static char            SCHEMA[] = _PATH_SCHEMA;
  71.     register FILE        *F;
  72.     register char        *p;
  73.     register ARTOVERFIELD    *fp;
  74.     register int        i;
  75.     char            buff[SMBUF];
  76.  
  77.     /* Open file, count lines. */
  78.     if ((F = fopen(SCHEMA, "r")) == NULL)
  79.     return;
  80.     for (i = 0; fgets(buff, sizeof buff, F) != NULL; i++)
  81.     continue;
  82.     (void)fseek(F, (OFFSET_T)0, SEEK_SET);
  83.     ARTfields = NEW(ARTOVERFIELD, i + 1);
  84.  
  85.     /* Parse each field. */
  86.     for (fp = ARTfields; fgets(buff, sizeof buff, F) != NULL; ) {
  87.     /* Ignore blank and comment lines. */
  88.     if ((p = strchr(buff, '\n')) != NULL)
  89.         *p = '\0';
  90.     if ((p = strchr(buff, COMMENT_CHAR)) != NULL)
  91.         *p = '\0';
  92.     if (buff[0] == '\0')
  93.         continue;
  94.     if ((p = strchr(buff, ':')) != NULL) {
  95.         *p++ = '\0';
  96.         fp->HasHeader = EQ(p, "full");
  97.     }
  98.     else
  99.         fp->HasHeader = FALSE;
  100.     fp->Header = COPY(buff);
  101.     fp->Length = strlen(buff);
  102.     fp++;
  103.     }
  104.     ARTfieldsize = fp - ARTfields;
  105.     (void)fclose(F);
  106. }
  107.  
  108.  
  109. /*
  110. **  If we have an article open, close it.
  111. */
  112. void
  113. ARTclose()
  114. {
  115.     if (ARTqp) {
  116.     QIOclose(ARTqp);
  117.     ARTqp = NULL;
  118.     }
  119. }
  120.  
  121.  
  122. /*
  123. **  Get the Message-ID from a file.
  124. */
  125. STATIC void
  126. ARTgetmsgid(qp, id)
  127.     register QIOSTATE    *qp;
  128.     char        *id;
  129. {
  130.     register char    *p;
  131.     register char    *q;
  132.  
  133.     for (*id = '\0'; (p = QIOread(qp)) != NULL && *p != '\0'; ) {
  134.     if (*p != 'M' && *p != 'm')
  135.         continue;
  136.     if ((q = strchr(p, ' ')) == NULL)
  137.         continue;
  138.     *q++ = '\0';
  139.     if (caseEQ(p, "Message-ID:")) {
  140.         (void)strcpy(id, q);
  141.         break;
  142.     }
  143.     }
  144.     (void)QIOrewind(qp);
  145. }
  146.  
  147.  
  148. /*
  149. **  If the article name is valid, open it and stuff in the ID.
  150. */
  151. STATIC BOOL
  152. ARTopen(name, id)
  153.     char        *name;
  154.     char        *id;
  155. {
  156.     static ARTNUM    save_artnum;
  157.     static char        save_artid[BIG_BUFFER];
  158.     struct stat        Sb;
  159.  
  160.     /* Re-use article if it's the same one. */
  161.     if (ARTqp != NULL) {
  162.     if (save_artnum == atol(name) && QIOrewind(ARTqp) != -1) {
  163.         if (id)
  164.         (void)strcpy(id, save_artid);
  165.         return TRUE;
  166.     }
  167.     QIOclose(ARTqp);
  168.     }
  169.  
  170.     /* Open it, make sure it's a regular file. */
  171.     if ((ARTqp = QIOopen(name, QIO_BUFFER)) == NULL)
  172.     return FALSE;
  173.     if (fstat(QIOfileno(ARTqp), &Sb) < 0 || !S_ISREG(Sb.st_mode)) {
  174.     QIOclose(ARTqp);
  175.     ARTqp = NULL;
  176.     return FALSE;
  177.     }
  178.     CloseOnExec(QIOfileno(ARTqp), TRUE);
  179.  
  180.     save_artnum = atol(name);
  181.     ARTgetmsgid(ARTqp, save_artid);
  182.     (void)strcpy(id, save_artid);
  183.     return TRUE;
  184. }
  185.  
  186.  
  187. /*
  188. **  Open the article for a given Message-ID.
  189. */
  190. STATIC QIOSTATE *
  191. ARTopenbyid(msg_id, ap)
  192.     char    *msg_id;
  193.     ARTNUM    *ap;
  194. {
  195.     QIOSTATE    *qp;
  196.     char    *p;
  197.     char    *q;
  198.  
  199.     *ap = 0;
  200.     if ((p = HISgetent(msg_id, FALSE)) == NULL)
  201.     return NULL;
  202.     if ((qp = QIOopen(p, QIO_BUFFER)) == NULL)
  203.     return NULL;
  204.     CloseOnExec(QIOfileno(qp), TRUE);
  205.     if ((q = strrchr(p, '/')) != NULL)
  206.     *q++ = '\0';
  207.     if (GRPlast[0] && EQ(p, GRPlast))
  208.     *ap = atol(q);
  209.     return qp;
  210. }
  211.  
  212.  
  213. /*
  214. **  Send a (part of) a file to stdout, doing newline and dot conversion.
  215. */
  216. STATIC void
  217. ARTsend(qp, what)
  218.     register QIOSTATE    *qp;
  219.     SENDTYPE        what;
  220. {
  221.     register char    *p;
  222.  
  223.     ARTcount++;
  224.     GRParticles++;
  225.  
  226.     /* Get the headers. */
  227.     for ( ; ; ) {
  228.     p = QIOread(qp);
  229.     if (p == NULL) {
  230.         if (QIOtoolong(qp))
  231.         continue;
  232.         break;
  233.     }
  234.     if (*p == '\0')
  235.         break;
  236.     if (what == STbody)
  237.         continue;
  238.     Printf("%s%s\r\n", *p == '.' ? "." : "", p);
  239.     }
  240.  
  241.     if (what == SThead) {
  242.     Printf(".\r\n");
  243.     return;
  244.     }
  245.  
  246.     if (what == STarticle)
  247.     Printf("\r\n");
  248.     for ( ; ; ) {
  249.     p = QIOread(qp);
  250.     if (p == NULL) {
  251.         if (QIOtoolong(qp))
  252.         continue;
  253.         break;
  254.     }
  255.     Printf("%s%s\r\n", *p == '.' ? "." : "", p);
  256.     }
  257.     Printf(".\r\n");
  258. }
  259.  
  260.  
  261. /*
  262. **  Find an article number in the article array via a binary search;
  263. **  return -1 if not found.  Cache last hit to make linear lookups
  264. **  faster.
  265. */
  266. STATIC int
  267. ARTfind(i)
  268.     register ARTNUM    i;
  269. {
  270.     register ARTNUM    *bottom;
  271.     register ARTNUM    *middle;
  272.     register ARTNUM    *top;
  273.  
  274.     if (ARTsize == 0)
  275.     return -1;
  276.  
  277.     top = &ARTnumbers[ARTsize - 1];
  278.     if (ARTcache && ++ARTcache <= top && *ARTcache <= i) {
  279.     if (*ARTcache == i)
  280.         return ARTcache - ARTnumbers;
  281.     bottom = ARTcache;
  282.     }
  283.     else {
  284.     ARTcache = NULL;
  285.     bottom = ARTnumbers;
  286.     }
  287.  
  288.     for ( ; ; ) {
  289.     if (i < *bottom || i > *top)
  290.         break;
  291.  
  292.     middle = bottom + (top - bottom) / 2;
  293.     if (i == *middle) {
  294.         /* Found it; update cache. */
  295.         ARTcache = middle;
  296.         return middle - ARTnumbers;
  297.     }
  298.  
  299.     if (i > *middle)
  300.         bottom = middle + 1;
  301.     else
  302.         top = middle;
  303.     }
  304.     return -1;
  305. }
  306.  
  307.  
  308. /*
  309. **  Ask the innd server for the article.  Only called from CMDfetch,
  310. **  and only if history file is buffered.  Common case:  "oops, cancel
  311. **  that article I just posted."
  312. */
  313. STATIC QIOSTATE *
  314. ARTfromboss(what, id)
  315.     SENDDATA        *what;
  316.     char        *id;
  317. {
  318.     FILE        *FromServer;
  319.     FILE        *ToServer;
  320.     QIOSTATE        *qp;
  321.     char        buff[NNTP_STRLEN + 2];
  322.     char        *name;
  323.     char        *p;
  324.     BOOL        more;
  325.  
  326.     /* If we can, open the connection. */
  327.     if (NNTPlocalopen(&FromServer, &ToServer, (char *)NULL) < 0)
  328.     return NULL;
  329.  
  330.     /* Send the query to the server. */
  331.     qp = NULL;
  332.     (void)fprintf(ToServer, "XPATH %s\r\n", id);
  333.     (void)fflush(ToServer);
  334.     if (ferror(ToServer))
  335.     goto QuitClose;
  336.  
  337.     /* Get the reply; article exist? */
  338.     if (fgets(buff, sizeof buff, FromServer) == NULL
  339.      || atoi(buff) == NNTP_DONTHAVEIT_VAL)
  340.     goto QuitClose;
  341.  
  342.     /* Yes.  Be quick if just doing a stat. */
  343.     if (what == &SENDstat) {
  344.     qp = QIOopen("/dev/null", 0);
  345.     goto QuitClose;
  346.     }
  347.  
  348.     /* Clean up response. */
  349.     if ((p = strchr(buff, '\r')) != NULL)
  350.     *p = '\0';
  351.     if ((p = strchr(buff, '\n')) != NULL)
  352.     *p = '\0';
  353.  
  354.     /* Loop over all filenames until we can open one. */
  355.     for (name = buff; *name; name = p + 1) {
  356.     /* Snip off next name, turn dots to slashes. */
  357.     for (p = name; ISWHITE(*p); p++)
  358.         continue;
  359.     for (name = p; *p && *p != ' '; p++)
  360.         if (*p == '.')
  361.         *p = '/';
  362.     more = *p == ' ';
  363.     if (more)
  364.         *p = '\0';
  365.     if ((qp = QIOopen(name, QIO_BUFFER)) != NULL || !more)
  366.         break;
  367.     }
  368.  
  369.     /* Send quit, read server's reply, close up and return. */
  370.   QuitClose:
  371.     (void)fprintf(ToServer, "quit\r\n");
  372.     (void)fclose(ToServer);
  373.     (void)fgets(buff, sizeof buff, FromServer);
  374.     (void)fclose(FromServer);
  375.     return qp;
  376. }
  377.  
  378.  
  379. /*
  380. **  Fetch part or all of an article and send it to the client.
  381. */
  382. FUNCTYPE
  383. CMDfetch(ac, av)
  384.     int            ac;
  385.     char        *av[];
  386. {
  387.     char        buff[SMBUF];
  388.     char        idbuff[BIG_BUFFER];
  389.     SENDDATA        *what;
  390.     register QIOSTATE    *qp;
  391.     register BOOL    ok;
  392.     ARTNUM        art;
  393.  
  394.     /* Find what to send; get permissions. */
  395.     ok = PERMcanread;
  396.     switch (*av[0]) {
  397.     default:
  398.     what = &SENDbody;
  399.     break;
  400.     case 'a': case 'A':
  401.     what = &SENDarticle;
  402.     break;
  403.     case 's': case 'S':
  404.     what = &SENDstat;
  405.     break;
  406.     case 'h': case 'H':
  407.     what = &SENDhead;
  408.     /* Poster might do a "head" command to verify the article. */
  409.     ok = PERMcanread || PERMcanpost;
  410.     break;
  411.     }
  412.  
  413.     if (!ok) {
  414.     Reply("%s\r\n", NOACCESS);
  415.     return;
  416.     }
  417.  
  418.     /* Requesting by Message-ID? */
  419.     if (ac == 2 && av[1][0] == '<') {
  420.     if ((qp = ARTopenbyid(av[1], &art)) == NULL
  421.      && (qp = ARTfromboss(what, av[1])) == NULL) {
  422.         Reply("%d No such article\r\n", NNTP_DONTHAVEIT_VAL);
  423.         return;
  424.     }
  425.     if (!PERMartok(qp)) {
  426.         QIOclose(qp);
  427.         Reply("%s\r\n", NOACCESS);
  428.         return;
  429.     }
  430.     Reply("%d %ld %s %s\r\n", what->ReplyCode, art, what->Item, av[1]);
  431.     if (what->Type != STstat)
  432.         ARTsend(qp, what->Type);
  433.     QIOclose(qp);
  434.     return;
  435.     }
  436.  
  437.     /* Trying to read. */
  438.     if (GRPcount == 0) {
  439.     Reply("%s\r\n", ARTnotingroup);
  440.     return;
  441.     }
  442.  
  443.     /* Default is to get current article, or specified article. */
  444.     if (ac == 1) {
  445.     if (ARTindex < 0 || ARTindex >= ARTsize) {
  446.         Reply("%s\r\n", ARTnocurrart);
  447.         return;
  448.     }
  449.     (void)sprintf(buff, "%ld", ARTnumbers[ARTindex]);
  450.     }
  451.     else {
  452.     if (strspn(av[1], "0123456789") != strlen(av[1])) {
  453.         Reply("%s\r\n", ARTnoartingroup);
  454.         return;
  455.     }
  456.     (void)strcpy(buff, av[1]);
  457.     }
  458.  
  459.     /* Move forward until we can find one. */
  460.     while (!ARTopen(buff, idbuff)) {
  461.     if (ac > 1 || ++ARTindex >= ARTsize) {
  462.         Reply("%s\r\n", ARTnoartingroup);
  463.         return;
  464.     }
  465.     (void)sprintf(buff, "%ld", ARTnumbers[ARTindex]);
  466.     }
  467.  
  468.     Reply("%d %s %s %s\r\n", what->ReplyCode, buff, idbuff, what->Item);
  469.     if (what->Type != STstat)
  470.     ARTsend(ARTqp, what->Type);
  471.     if (ac > 1)
  472.     ARTindex = ARTfind((ARTNUM)atol(buff));
  473. }
  474.  
  475.  
  476. /*
  477. **  Go to the next or last (really previous) article in the group.
  478. */
  479. FUNCTYPE
  480. CMDnextlast(ac, av)
  481.     int        ac;
  482.     char    *av[];
  483. {
  484.     char    buff[SPOOLNAMEBUFF];
  485.     char    idbuff[SMBUF];
  486.     int        save;
  487.     BOOL    next;
  488.     int        delta;
  489.     int        errcode;
  490.     STRING    message;
  491.  
  492.     if (!PERMcanread) {
  493.     Reply("%s\r\n", NOACCESS);
  494.     return;
  495.     }
  496.     if (GRPcount == 0) {
  497.     Reply("%s\r\n", ARTnotingroup);
  498.     return;
  499.     }
  500.     if (ARTindex < 0 || ARTindex >= ARTsize) {
  501.     Reply("%s\r\n", ARTnocurrart);
  502.     return;
  503.     }
  504.  
  505.     next = (av[0][0] == 'n' || av[0][0] == 'N');
  506.     if (next) {
  507.     delta = 1;
  508.     errcode = NNTP_NONEXT_VAL;
  509.     message = "next";
  510.     }
  511.     else {
  512.     delta = -1;
  513.     errcode = NNTP_NOPREV_VAL;
  514.     message = "previous";
  515.     }
  516.  
  517.     save = ARTindex;
  518.     ARTindex += delta;
  519.     if (ARTindex < 0 || ARTindex >= ARTsize) {
  520.     Reply("%d No %s to retrieve.\r\n", errcode, message);
  521.     ARTindex = save;
  522.     return;
  523.     }
  524.  
  525.     (void)sprintf(buff, "%ld", ARTnumbers[ARTindex]);
  526.     while (!ARTopen(buff, idbuff)) {
  527.     ARTindex += delta;
  528.     if (ARTindex < 0 || ARTindex >= ARTsize) {
  529.         Reply("%d No %s article to retrieve.\r\n", errcode, message);
  530.         ARTindex = save;
  531.         return;
  532.     }
  533.     (void)sprintf(buff, "%ld", ARTnumbers[ARTindex]);
  534.     }
  535.  
  536.     Reply("%d %s %s Article retrieved; request text separately.\r\n",
  537.        NNTP_NOTHING_FOLLOWS_VAL, buff, idbuff);
  538.  
  539.     if (ac > 1)
  540.     ARTindex = ARTfind((ARTNUM)atol(buff));
  541. }
  542.  
  543.  
  544. /*
  545. **  Return the header from the specified file, or NULL if not found.
  546. **  We can estimate the Lines header, if that's what's wanted.
  547. */
  548. STATIC char *
  549. GetHeader(qp, header, IsLines)
  550.     register QIOSTATE    *qp;
  551.     register char    *header;
  552.     BOOL        IsLines;
  553. {
  554.     static char        buff[40];
  555.     register char    *p;
  556.     register char    *q;
  557.     struct stat        Sb;
  558.  
  559.     for ( ; ; ) {
  560.     if ((p = QIOread(qp)) == NULL) {
  561.         if (QIOtoolong(qp))
  562.         continue;
  563.         break;
  564.     }
  565.     if (*p == '\0')
  566.         /* End of headers. */
  567.         break;
  568.     if (ISWHITE(*p) || (q = strchr(p, ':')) == NULL)
  569.         /* Continuation or bogus (shouldn't happen) line; ignore. */
  570.         continue;
  571.     *q = '\0';
  572.     if (caseEQ(header, p))
  573.         return *++q ? q + 1 : NULL;
  574.     }
  575.  
  576.     if (IsLines && fstat(QIOfileno(qp), &Sb) >= 0) {
  577.     /* Lines estimation taken from Tor Lillqvist <tml@tik.vtt.fi>'s
  578.      * posting <TML.92Jul10031233@hemuli.tik.vtt.fi> in
  579.      * news.sysadmin. */
  580.     (void)sprintf(buff, "%d",
  581.         (int)(6.4e-8 * Sb.st_size * Sb.st_size + 0.023 * Sb.st_size - 12));
  582.     return buff;
  583.     }
  584.     return NULL;
  585. }
  586.  
  587.  
  588. STATIC BOOL
  589. CMDgetrange(ac, av, rp)
  590.     int            ac;
  591.     char        *av[];
  592.     register ARTRANGE    *rp;
  593. {
  594.     register char    *p;
  595.  
  596.     if (GRPcount == 0) {
  597.     Reply("%s\r\n", ARTnotingroup);
  598.     return FALSE;
  599.     }
  600.  
  601.     if (ac == 1) {
  602.     /* No argument, do only current article. */
  603.     if (ARTindex < 0 || ARTindex >= ARTsize) {
  604.         Reply("%s\r\n", ARTnocurrart);
  605.         return FALSE;
  606.     }
  607.     rp->High = rp->Low = ARTnumbers[ARTindex];
  608.     return TRUE;
  609.     }
  610.  
  611.     /* Got just a single number? */
  612.     if ((p = strchr(av[1], '-')) == NULL) {
  613.     rp->Low = rp->High = atol(av[1]);
  614.     return TRUE;
  615.     }
  616.  
  617.     /* Parse range. */
  618.     *p++ = '\0';
  619.     rp->Low = atol(av[1]);
  620.     if (ARTsize) {
  621.     if (*p == '\0' || (rp->High = atol(p)) < rp->Low)
  622.         /* "XHDR 234-0 header" gives everything to the end. */
  623.         rp->High = ARTnumbers[ARTsize - 1];
  624.     else if (rp->High > ARTnumbers[ARTsize - 1])
  625.         rp->High = ARTnumbers[ARTsize - 1];
  626.     if (rp->Low < ARTnumbers[0])
  627.         rp->Low = ARTnumbers[0];
  628.     }
  629.     else
  630.     /* No articles; make sure loops don't run. */
  631.     rp->High = rp->Low ? rp->Low - 1 : 0;
  632.     return TRUE;
  633. }
  634.  
  635.  
  636. /*
  637. **  Return a field from the overview line or NULL on error.  Return a copy
  638. **  since we might be re-using the line later.
  639. */
  640. STATIC char *
  641. OVERGetHeader(p, field)
  642.     register char    *p;
  643.     int            field;
  644. {
  645.     static char        *buff;
  646.     static int        buffsize;
  647.     register int    i;
  648.     ARTOVERFIELD    *fp;
  649.     char        *next;
  650.  
  651.     /* Skip leading headers. */
  652.     for (fp = &ARTfields[field - 1]; --field >= 0 && *p; p++)
  653.     if ((p = strchr(p, '\t')) == NULL)
  654.         return NULL;
  655.     if (*p == '\0')
  656.     return NULL;
  657.  
  658.     if (fp->HasHeader)
  659.     p += fp->Length + 2;
  660.  
  661.     /* Figure out length; get space. */
  662.     if ((next = strchr(p, '\t')) != NULL)
  663.     i = next - p;
  664.     else
  665.     i = strlen(p);
  666.     if (buffsize == 0) {
  667.     buffsize = i;
  668.     buff = NEW(char, buffsize + 1);
  669.     }
  670.     else if (buffsize < i) {
  671.     buffsize = i;
  672.     RENEW(buff, char, buffsize + 1);
  673.     }
  674.  
  675.     (void)strncpy(buff, p, i);
  676.     buff[i] = '\0';
  677.     return buff;
  678. }
  679.  
  680.  
  681. /*
  682. **  Open an OVERVIEW file.
  683. */
  684. STATIC BOOL
  685. OVERopen()
  686. {
  687.     char    name[SPOOLNAMEBUFF];
  688.  
  689.     /* Already open? */
  690.     if (OVERqp != NULL)
  691.     /* Don't rewind -- we are probably going forward via repeated
  692.      * NNTP commands. */
  693.     return TRUE;
  694.  
  695.     /* Failed here before? */
  696.     if (OVERopens++)
  697.     return FALSE;
  698.  
  699.     OVERline = NULL;
  700.     OVERarticle = 0;
  701.     (void)sprintf(name, "%s/%s/%s", _PATH_OVERVIEWDIR, GRPlast, _PATH_OVERVIEW);
  702.     OVERqp = QIOopen(name, QIO_BUFFER);
  703.     return OVERqp != NULL;
  704. }
  705.  
  706.  
  707. /*
  708. **  Close the OVERVIEW file.
  709. */
  710. void
  711. OVERclose()
  712. {
  713.     if (OVERqp != NULL) {
  714.     QIOclose(OVERqp);
  715.     OVERqp = NULL;
  716.     OVERopens = 0;
  717.     }
  718. }
  719.  
  720.  
  721. /*
  722. **  Return the overview data for an article or NULL on failure.
  723. **  Assumes that what we return is never modified.
  724. */
  725. STATIC char *
  726. OVERfind(artnum)
  727.     ARTNUM    artnum;
  728. {
  729.     if (OVERqp == NULL)
  730.     return NULL;
  731.  
  732.     if (OVERarticle > artnum) {
  733.     (void)QIOrewind(OVERqp);
  734.     OVERarticle = 0;
  735.     OVERline = NULL;
  736.     }
  737.  
  738.     for ( ; OVERarticle < artnum; OVERarticle = atol(OVERline))
  739.     if ((OVERline = QIOread(OVERqp)) == NULL) {
  740.         if (QIOtoolong(OVERqp))
  741.         continue;
  742.         /* Don't close file; we may rewind. */
  743.         return NULL;
  744.     }
  745.  
  746.     return OVERarticle == artnum ? OVERline : NULL;
  747. }
  748.  
  749.  
  750. /*
  751. **  Read an article and create an overview line without the trailing
  752. **  newline.  Returns pointer to static space or NULL on error.
  753. */
  754. STATIC char *
  755. OVERgen(name)
  756.     char            *name;
  757. {
  758.     static ARTOVERFIELD        *Headers;
  759.     static char            *buff;
  760.     static int            buffsize;
  761.     register ARTOVERFIELD    *fp;
  762.     register ARTOVERFIELD    *hp;
  763.     register QIOSTATE        *qp;
  764.     register char        *colon;
  765.     register char        *line;
  766.     register char        *p;
  767.     register int        i;
  768.     register int        size;
  769.     register int        ov_size;
  770.     register long        lines;
  771.     struct stat            Sb;
  772.     long            t;
  773.     char            value[10];
  774.  
  775.     /* Open article. */
  776.     if ((qp = QIOopen(name, QIO_BUFFER)) == NULL)
  777.     return NULL;
  778.     if ((p = strrchr(name, '/')) != NULL)
  779.     name = p + 1;
  780.  
  781.     /* Set up place to store headers. */
  782.     if (Headers == NULL) {
  783.     Headers = NEW(ARTOVERFIELD, ARTfieldsize);
  784.     for (hp = Headers, i = ARTfieldsize; --i >= 0; hp++)
  785.         hp->Length = 0;
  786.     }
  787.     for (hp = Headers, i = ARTfieldsize; --i >= 0; hp++)
  788.     hp->HasHeader = FALSE;
  789.  
  790.     for ( ; ; ) {
  791.     /* Read next line. */
  792.     if ((line = QIOread(qp)) == NULL) {
  793.         if (QIOtoolong(qp))
  794.         continue;
  795.         /* Error or EOF (in headers!?); shouldn't happen. */
  796.         QIOclose(qp);
  797.         return NULL;
  798.     }
  799.  
  800.     /* End of headers? */
  801.     if (*line == '\0')
  802.         break;
  803.  
  804.     /* See if we want this header. */
  805.     fp = ARTfields;
  806.     for (hp = Headers, i = ARTfieldsize; --i >= 0; hp++, fp++) {
  807.         colon = &line[fp->Length];
  808.         if (*colon != ':')
  809.         continue;
  810.         *colon = '\0';
  811.         if (!caseEQ(line, fp->Header)) {
  812.         *colon = ':';
  813.         continue;
  814.         }
  815.         *colon = ':';
  816.         if (fp->HasHeader)
  817.         p = line;
  818.         else
  819.         /* Skip colon and whitespace, store value. */
  820.         for (p = colon; *++p && ISWHITE(*p); )
  821.             continue;
  822.         size = strlen(p);
  823.         if (hp->Length == 0) {
  824.         hp->Length = size;
  825.         hp->Header = NEW(char, hp->Length + 1);
  826.         }
  827.         else if (hp->Length < size) {
  828.         hp->Length = size;
  829.         RENEW(hp->Header, char, hp->Length + 1);
  830.         }
  831.         (void)strcpy(hp->Header, p);
  832.         for (p = hp->Header; *p; p++)
  833.         if (*p == '\t' || *p == '\n')
  834.             *p = ' ';
  835.         hp->HasHeader = TRUE;
  836.     }
  837.     }
  838.  
  839.     /* Read body of article, just to get lines. */
  840.     for (lines = 0; ; lines++)
  841.     if ((p = QIOread(qp)) == NULL) {
  842.         if (QIOtoolong(qp))
  843.         continue;
  844.         if (QIOerror(qp)) {
  845.         QIOclose(qp);
  846.         return NULL;
  847.         }
  848.         break;
  849.     }
  850.  
  851.     /* Calculate total size, fix hardwired headers. */
  852.     ov_size = strlen(name) + ARTfieldsize + 2;
  853.     for (hp = Headers, fp = ARTfields, i = ARTfieldsize; --i >= 0; hp++, fp++) {
  854.     if (caseEQ(fp->Header, "Bytes") || caseEQ(fp->Header, "Lines")) {
  855.         if (fp->Header[0] == 'B' || fp->Header[0] == 'b')
  856.         t = fstat(QIOfileno(qp), &Sb) >= 0 ? (long)Sb.st_size : 0L;
  857.         else
  858.         t = lines;
  859.  
  860.         (void)sprintf(value, "%ld", t);
  861.         size = strlen(value);
  862.         if (hp->Length == 0) {
  863.          hp->Length = size;
  864.         hp->Header = NEW(char, hp->Length + 1);
  865.         }
  866.         else if (hp->Length < size) {
  867.         hp->Length = size;
  868.         RENEW(hp->Header, char, hp->Length + 1);
  869.         }
  870.         (void)strcpy(hp->Header, value);
  871.         hp->HasHeader = TRUE;
  872.     }
  873.     if (hp->HasHeader)
  874.         ov_size += strlen(hp->Header);
  875.     }
  876.  
  877.     /* Get space. */
  878.     if (buffsize == 0) {
  879.     buffsize = ov_size;
  880.     buff = NEW(char, buffsize + 1);
  881.     }
  882.     else if (buffsize < ov_size) {
  883.     buffsize = ov_size;
  884.     RENEW(buff, char, buffsize + 1);
  885.     }
  886.  
  887.     /* Glue all the fields together. */
  888.     p = buff + strlen(strcpy(buff, name));
  889.     for (hp = Headers, i = ARTfieldsize; --i >= 0; hp++) {
  890.     *p++ = '\t';
  891.     if (hp->HasHeader)
  892.         p += strlen(strcpy(p, hp->Header));
  893.     }
  894.     *p = '\0';
  895.  
  896.     QIOclose(qp);
  897.     return buff;
  898. }
  899.  
  900.  
  901. /*
  902. **  XHDR, a common extension.  Retrieve specified header from a
  903. **  Message-ID or article range.
  904. */
  905. FUNCTYPE
  906. CMDxhdr(ac, av)
  907.     int            ac;
  908.     char        *av[];
  909. {
  910.     register QIOSTATE    *qp;
  911.     register ARTNUM    i;
  912.     register char    *p;
  913.     int            Overview;
  914.     BOOL        IsLines;
  915.     ARTRANGE        range;
  916.     char        buff[SPOOLNAMEBUFF];
  917.     ARTNUM        art;
  918.  
  919.     if (!PERMcanread) {
  920.     Reply("%s\r\n", NOACCESS);
  921.     return;
  922.     }
  923.     IsLines = caseEQ(av[1], "lines");
  924.  
  925.     /* Message-ID specified? */
  926.     if (ac == 3 && av[2][0] == '<') {
  927.     if ((qp = ARTopenbyid(av[2], &art)) == NULL) {
  928.         Reply("%d No such article\r\n", NNTP_DONTHAVEIT_VAL);
  929.         return;
  930.     }
  931.     Reply("%d %ld %s header of article %s.\r\n",
  932.        NNTP_HEAD_FOLLOWS_VAL, art, av[1], av[2]);
  933.     p = GetHeader(qp, av[1], IsLines);
  934.     Printf("%s %s\r\n", av[2], p ? p : "(none)");
  935.     QIOclose(qp);
  936.     Printf(".\r\n");
  937.     return;
  938.     }
  939.  
  940.     /* Range specified. */
  941.     if (!CMDgetrange(ac - 1, av + 1, &range))
  942.     return;
  943.  
  944.     /* Is this a header in our overview? */
  945.     for (Overview = 0, i = 0; i < ARTfieldsize; i++)
  946.     if (caseEQ(ARTfields[i].Header, av[1])) {
  947.         if (OVERopen())
  948.         Overview = i + 1;
  949.         break;
  950.     }
  951.  
  952.     Reply("%d %s fields follow\r\n", NNTP_HEAD_FOLLOWS_VAL, av[1]);
  953.     for (i = range.Low; i <= range.High; i++) {
  954.     if (ARTfind(i) < 0)
  955.         continue;
  956.  
  957.     /* Get it from the overview? */
  958.     if (Overview && (p = OVERfind(i)) != NULL) {
  959.         p = OVERGetHeader(p, Overview);
  960.         Printf("%d %s\r\n", i, p && *p ? p : "(none)");
  961.         continue;
  962.     }
  963.  
  964.     (void)sprintf(buff, "%ld", i);
  965.     if ((qp = QIOopen(buff, QIO_BUFFER)) == NULL)
  966.         continue;
  967.     p = GetHeader(qp, av[1], IsLines);
  968.     Printf("%d %s\r\n", i, p ? p : "(none)");
  969.     QIOclose(qp);
  970.     }
  971.     Printf(".\r\n");
  972. }
  973.  
  974.  
  975. /*
  976. **  XOVER another extension.  Dump parts of the overview database.
  977. */
  978. FUNCTYPE
  979. CMDxover(ac, av)
  980.     int            ac;
  981.     char        *av[];
  982. {
  983.     register char    *p;
  984.     register ARTNUM    i;
  985.     register BOOL    Opened;
  986.     ARTRANGE        range;
  987.     char        buff[SPOOLNAMEBUFF];
  988.  
  989.     if (!PERMcanread) {
  990.     Printf("%s\r\n", NOACCESS);
  991.     return;
  992.     }
  993.  
  994.     /* Trying to read. */
  995.     if (GRPcount == 0) {
  996.     Reply("%s\r\n", ARTnotingroup);
  997.     return;
  998.     }
  999.  
  1000.     /* Parse range. */
  1001.     if (!CMDgetrange(ac, av, &range))
  1002.     return;
  1003.  
  1004.     Reply("%d data follows\r\n", NNTP_OVERVIEW_FOLLOWS_VAL);
  1005.     for (Opened = OVERopen(), i = range.Low; i <= range.High; i++) {
  1006.     if (ARTfind(i) < 0)
  1007.         continue;
  1008.  
  1009.     if (Opened && (p = OVERfind(i)) != NULL) {
  1010.         Printf("%s\r\n", p);
  1011.         continue;
  1012.     }
  1013.  
  1014.     (void)sprintf(buff, "%ld", i);
  1015.     if ((p = OVERgen(buff)) != NULL)
  1016.         Printf("%s\r\n", p);
  1017.     }
  1018.     Printf(".\r\n");
  1019. }
  1020.  
  1021.  
  1022. /*
  1023. **  XPAT, an uncommon extension.  Print only headers that match the pattern.
  1024. */
  1025. /* ARGSUSED */
  1026. FUNCTYPE
  1027. CMDxpat(ac, av)
  1028.     int            ac;
  1029.     char        *av[];
  1030. {
  1031.     register char    *p;
  1032.     register QIOSTATE    *qp;
  1033.     register ARTNUM    i;
  1034.     ARTRANGE        range;
  1035.     char        *header;
  1036.     char        *pattern;
  1037.     char        *text;
  1038.     int            Overview;
  1039.     char        buff[SPOOLNAMEBUFF];
  1040.     ARTNUM        art;
  1041.  
  1042.     if (!PERMcanread) {
  1043.     Printf("%s\r\n", NOACCESS);
  1044.     return;
  1045.     }
  1046.  
  1047.     header = av[1];
  1048.  
  1049.     /* Message-ID specified? */
  1050.     if (av[2][0] == '<') {
  1051.     p = av[2];
  1052.     qp = ARTopenbyid(p, &art);
  1053.     if (qp == NULL) {
  1054.         Printf("%d No such article.\r\n", NNTP_DONTHAVEIT_VAL);
  1055.         return;
  1056.     }
  1057.  
  1058.     Printf("%d %s matches follow.\r\n", NNTP_HEAD_FOLLOWS_VAL, header);
  1059.     pattern = Glom(&av[3]);
  1060.     if ((text = GetHeader(qp, header, FALSE)) != NULL
  1061.      && wildmat(text, pattern))
  1062.         Printf("%s %s\r\n", p, text);
  1063.  
  1064.     QIOclose(qp);
  1065.     Printf(".\r\n");
  1066.     DISPOSE(pattern);
  1067.     return;
  1068.     }
  1069.  
  1070.     /* Range specified. */
  1071.     if (!CMDgetrange(ac - 1, av + 1, &range))
  1072.     return;
  1073.  
  1074.     /* In overview? */
  1075.     for (Overview = 0, i = 0; i < ARTfieldsize; i++)
  1076.     if (caseEQ(ARTfields[i].Header, av[1])) {
  1077.         if (OVERopen())
  1078.         Overview = i + 1;
  1079.         break;
  1080.     }
  1081.  
  1082.     Printf("%d %s matches follow.\r\n", NNTP_HEAD_FOLLOWS_VAL, header);
  1083.     for (pattern = Glom(&av[3]), i = range.Low; i < range.High; i++) {
  1084.     if (ARTfind(i) < 0)
  1085.         continue;
  1086.  
  1087.     /* Get it from the Overview? */
  1088.     if (Overview
  1089.      && (p = OVERfind(i)) != NULL
  1090.      && (p = OVERGetHeader(p, Overview)) != NULL) {
  1091.         if (wildmat(p, pattern))
  1092.         Printf("%ld %s\r\n", i, p);
  1093.         continue;
  1094.     }
  1095.  
  1096.     (void)sprintf(buff, "%ld", i);
  1097.     if ((qp = QIOopen(buff, QIO_BUFFER)) == NULL)
  1098.         continue;
  1099.     if ((p = GetHeader(qp, av[1], FALSE)) == NULL)
  1100.         p = "(none)";
  1101.     if (wildmat(p, pattern))
  1102.         Printf("%ld %s\r\n", i, p);
  1103.     QIOclose(qp);
  1104.     }
  1105.  
  1106.     Printf(".\r\n");
  1107.     DISPOSE(pattern);
  1108. }
  1109.